<?php

namespace funjob\Helper;

/**
 * 地图围栏助手
 */
class MapFence
{

    /**
     * 获取计算距离
     * 
     * @param int    $uid 支付用户ID
     * @param int    $start 起点经纬度
     * @param string $end 终点经纬度
     * @param string|bool $unit 单位
     * @return string|decimal
     */
    static function getDistanceUnit($start = '', $end = '', $unit = false)
    {
        $distance = self::getDistance($start, $end);
        return self::enUnit($distance, $unit);
    }
    /**
     * 原始
     */
    static function getDistance($start = '', $end = '')
    {
        $start = is_array($start) ? $start : explode(',', $start);
        $end = is_array($end) ? $end : explode(',', $end);
        list($start_lat, $start_lng) = $start;
        list($end_lat, $end_lng) = $end;
        $rad_start_lat = $start_lat * M_PI / 180.0;
        $rad_start_lng = $start_lng * M_PI / 180.0;
        $rad_end_lng = $end_lng * M_PI / 180.0;
        $rad_end_lat = $end_lat * M_PI / 180.0;
        $EARTH_RADIUS = 6378137.0; // 地球半径系数(米)  小：6370996；大：6378245
        $c = 2 * asin(sqrt(pow(sin(($rad_start_lat - $rad_end_lat) / 2), 2) + cos($rad_start_lat) * cos($rad_end_lat) * pow(sin(($rad_start_lng - $rad_end_lng) / 2), 2)));
        $distance = $c * $EARTH_RADIUS;
        return $distance;
    }

    /**
     * 单位换算
     */
    public static function enUnit($v, $unit = false)
    {
        if ($unit === true) {
            $unit = $v > 1000 ? 'km' : 'm';
        }
        if (in_array($unit, ['km', 'Km', 'KM', '公里', '千米'])) {
            return round($v / 1000, 2) . $unit;
        }
        $distance = round($v, 2);
        if (in_array($unit, ['M', 'm', '米'])) {
            $distance = $distance . $unit;
        }
        return $distance;
    }
    /**
     * 单位解析
     */
    public static function deUnit($content)
    {
        $content = preg_replace('/\s+/', '', $content);
        preg_match_all("/[\d]+|[KM|Km|km|公里|千米|M|m|米]+/", $content, $match);
        $data = isset($match[0]) ? $match[0] : [0];
        $distance = $data[0];
        $unit = isset($data[1]) ? $data[1] : '';
        if (in_array($unit, ['KM', 'Km', 'km', '公里', '千米'])) {
            $distance = round($distance * 1000, 2);
        }
        return $distance;
    }

    /**
     * 计算边距 (海伦公式)
     */
    public static function getSideDistance($ab, $ac, $bc)
    {
        // 计算面积
        $s = ($ab + $ac + $bc) / 2;
        $area = sqrt($s * ($s - $ab) * ($s - $ac) * ($s - $bc));
        $distance = round(2 * $area / $bc, 2);
        // 计算角度
        $abc = rad2deg(acos(($ab * $ab + $bc * $bc - $ac * $ac) / (2 * $ab * $bc)));
        $bac = rad2deg(acos(($bc * $bc + $ac * $ac - $ab * $ab) / (2 * $bc * $ac)));
        $cab = rad2deg(acos(($ac * $ac + $ab * $ab - $bc * $bc) / (2 * $ac * $ab)));
        if ($abc > 90 || $bac > 90) {
            $distance = min([$ab, $ac]);
        }
        return $distance;
    }

    /**
     * 计算围栏距离（内部）
     *
     * @param string $point 要判断的坐标
     * @param array $fence 围栏，坐标数组
     * @return int
     */
    static function getFenceDistances($point, $data)
    {
        $list = [];
        $status = 0;
        foreach ($data as $ovs) {
            $list[] = self::getFenceDistance($point, $ovs);
        }
        $min_distance = min($list);
        $index = array_search($min_distance, $list);
        return ['status' => $status, 'distance' => $min_distance, 'index' => $index, 'list' => $list];
    }
    /**
     * 计算距离
     */
    static function getFenceDistance($point, $ovs)
    {
        $status = 0;
        $min = -1;
        $distance_array = [];
        // 边形
        if ($ovs['type'] == 'polygon' || $ovs['type'] == 'rectangle') {
            $fence = array_unique($ovs['paths']); // 过滤重叠点
            $sopt_count = count($fence);
            $distance_point = [];
            foreach ($fence as $key => $latlng) {
                $distance = self::getDistance($point, $latlng);
                $next_key = $key + 1 >= $sopt_count ? 0 : $key + 1;
                $nextdistance = self::getDistance($point, $fence[$next_key]);
                $sidedistance = self::getDistance($latlng, $fence[$next_key]);
                $distance_array[] = round(self::getSideDistance($distance, $nextdistance, $sidedistance), 2);
                $distance_point[] = $distance;
            }
            if (self::inFence($point, $ovs)) {
                $status = 1;
            }
            $min = min($distance_array);
            $index = array_search(min($distance_point), $distance_point);
            $labelpoint = $fence[$index];
            if ($ovs['type'] == 'rectangle') {
                $labelpoint = $ovs['center'];
            } else {
                $centroid = self::polygonCenter($fence);
                $labelpoint = $centroid['lat'] . ',' . $centroid['lng'];
            }
        }
        // 圆形
        if ($ovs['type'] == 'circle') {
            $distance = self::getDistance($point, $ovs['center']);
            if ($distance <= $ovs['radius']) {
                $status = 1;
                $distance = $ovs['radius'] - $distance;
            } else {
                $status = 0;
                $distance = $distance - $ovs['radius'];
            }
            $min = round($distance, 2);
            $labelpoint = $ovs['center'];
        }
        // 折线
        if ($ovs['type'] == 'polyline') {
            $fence = array_unique($ovs['paths']); // 过滤重叠点
            $sopt_count = count($fence);
            foreach ($fence as $key => $latlng) {
                $distance = self::getDistance($point, $latlng);
                if ($distance == 0) {
                    $status = 1;
                }
                $distance_array[] = round($distance, 2);
            }
            $min = min($distance_array);
            $index = array_search($min, $distance_array);
            $labelpoint = $fence[$index];
        }

        // 标记点
        if ($ovs['type'] == 'marker') {
            $distance = self::getDistance($point, $ovs['position']);
            if ($distance == 0) {
                $status = 1;
            } else {
                $status = 0;
            }
            $min = round($distance, 2);
            $labelpoint = $ovs['position'];
        }
        return ['type' => $ovs['type'], 'status' => $status, 'distance' => $min, 'labelpoint' => $labelpoint];
    }

    /**
     * 计算围距离（外部）
     */
    static function getFenceDistanceOut($point, $data)
    {
        foreach ($data as $ovs) {
            $fence_arr = [];
            $distance_arr = [];
            $side_distance = [];
            if ($ovs['type'] == 'polygon' || $ovs['type'] == 'rectangle') {
                $fence = $ovs['paths'];
                $sopt_count = count($fence);
                foreach ($fence as $ov) {
                    $distance = self::getDistance($point, $ov);
                    $fence_arr[] = ['point' => $ov, 'distance' => $distance];
                    $distance_arr[] = $distance;
                }
                $last = $next = null;
                // 获取最近点
                $min = min($distance_arr);
                $index = array_search($min, $distance_arr);
                $start = $fence_arr[$index];
                // 向上偏移点
                $last_index = $index == 0 ? $sopt_count - 1 : $index - 1;
                $last = $fence_arr[$last_index];
                $last['side'] = self::getDistance($last['point'], $start['point']);
                // 向下偏移点
                $next_index = $index + 1 >= $sopt_count ? 0 : $index + 1;
                $next = $fence_arr[$next_index];
                $next['side'] = self::getDistance($next['point'], $start['point']);
                $side_distance = [
                    self::getSideDistance($start['distance'], $last['distance'], $last['side']),
                    self::getSideDistance($start['distance'], $next['distance'], $next['side']),
                ];
            }
            // 圆形
            if ($ovs['type'] == 'circle') {
                $distance = self::getDistance($point, $ovs['center']);
                $side_distance[] = round($distance - $ovs['radius'], 2);
            }
            $min_distance = min($side_distance);
            $list[] = min($side_distance);
        }
        $min_distance = min($list);
        $index = array_search($min_distance, $list);
        return ['status' => 0, 'distance' => $min_distance, 'index' => $index, 'list' => $list];
    }

    /**
     * 检测是否在区间（多个）
     * @param string $point 要判断的坐标
     * @param array $fence 围栏，坐标数组
     */
    static function inFences($point, $fence)
    {
        $status = 0;
        foreach ($fence as $key => $ovs) {
            if (self::inFence($point, $ovs)) {
                $status = 1;
                break;
            }
        }
        return $status;
    }

    /**
     * 自动判断围栏类型并检查（单个）
     */
    static function inFence($point, $data)
    {
        $rs = 0;
        if ($data['type'] == 'polygon' || $data['type'] == 'rectangle') {
            $rs = self::inFencePolygon($point, $data['paths']);
        }
        if ($data['type'] == 'circle') {
            $rs = self::inFenceCircle($point, $data);
        }
        return $rs;
    }

    /**
     * 圆形形围栏
     */
    static function inFenceCircle($point, $fence)
    {
        $rs = 0;
        $distance = self::getDistance($point, $fence['center']);
        if ($distance <= $fence['radius']) {
            $rs = 1;
        }
        return $rs;
    }

    /**
     * 多边形围栏
     */
    static function inFencePolygon($point, $fence)
    {
        $fence = is_array($fence) ? $fence : explode(',', $fence);
        list($x, $y) = is_array($point) ? $point : explode(',', $point);
        $count = count($fence);
        $n = 0;
        $bool = 0;
        for ($i = 0, $j = $count - 1; $i < $count; $j = $i, $i++) {
            list($px1, $py1) = (is_array($fence[$i])) ? $fence[$i] : explode(',', $fence[$i]);
            list($px2, $py2) = (is_array($fence[$j])) ? $fence[$j] : explode(',', $fence[$j]);
            if ($x >= $px1 || $x >= $px2) {
                if (($y >= $py1 && $y <= $py2) || ($y >= $py2 && $y <= $py1)) {
                    if (($y == $py1 && $x == $px1) || ($y == $py2 && $x == $px2)) {
                        $bool = 1; //在点上
                        return $bool;
                    } else {
                        $px = $px1 + ($y - $py1) / ($py2 - $py1) * ($px2 - $px1);
                        if ($px == $x) {
                            $bool = 1; //在线上
                            return $bool;
                        } elseif ($px < $x) {
                            $n++;
                        }
                    }
                }
            }
        }
        if ($n % 2 != 0) {
            $bool = 1;
        }
        return $bool;
    }

    /**
     * 计算重心点
     */
    static function polygon_centroid($vertices)
    {
        foreach ($vertices as $key => $os) {
            list($lat, $lng) = is_array($os) ? $os : explode(',', $os);
            $vertices[$key] = ['lat' => $lat, 'lng' => $lng];
        }
        $vertex_count = count($vertices);
        $sum_x = $sum_y = $sum_c = 0;
        for ($i = 0; $i < $vertex_count; ++$i) {
            $j = ($i + 1) % $vertex_count;
            $c = ($vertices[$i]['lat'] * $vertices[$j]['lng'] - $vertices[$j]['lat'] * $vertices[$i]['lng']);
            $sum_c += $c;
            $sum_x += ($vertices[$i]['lat'] + $vertices[$j]['lat']) * $c;
            $sum_y += ($vertices[$i]['lng'] + $vertices[$j]['lng']) * $c;
        }
        $factor = 1 / (3 * $sum_c);
        $x = $sum_x * $factor;
        $y = $sum_y * $factor;
        return ['lat' => $x, 'lng' => $y];
    }

    /**
     * 计算重心点
     */
    static function polygon_center($vertices)
    {
        foreach ($vertices as $key => $os) {
            list($lat, $lng) = is_array($os) ? $os : explode(',', $os);
            $vertices[$key] = ['lat' => $lat, 'lng' => $lng];
        }
        $vertex_count = count($vertices);
        $x_sum = $y_sum = $area = 0;
        for ($i = 0; $i < $vertex_count; ++$i) {
            $j = ($i + 1) % $vertex_count;
            $cross_product = $vertices[$i]['lat'] * $vertices[$j]['lng'] - $vertices[$j]['lat'] * $vertices[$i]['lng'];
            $area += $cross_product;
            $x_sum += ($vertices[$i]['lat'] + $vertices[$j]['lat']) * $cross_product;
            $y_sum += ($vertices[$i]['lng'] + $vertices[$j]['lng']) * $cross_product;
        }
        $area /= 2;
        $centroid_x = $x_sum / (6 * $area);
        $centroid_y = $y_sum / (6 * $area);
        return ['lat' => $centroid_x, 'lng' => $centroid_y];
    }

    /**
     * 计算中心点一
     */
    static function polygonCenter($points)
    {
        foreach ($points as $key => $os) {
            list($lat, $lng) = is_array($os) ? $os : explode(',', $os);
            $points[$key] = ['lat' => $lat, 'lng' => $lng];
        }
        $n = count($points);
        $x = $y = 0;
        foreach ($points as $point) {
            $x += $point['lat'];
            $y += $point['lng'];
        }
        return array('lat' => $x / $n, 'lng' => $y / $n);
    }
}
